package org.courierframework.core.repository.support;

import org.courierframework.core.repository.PaginationRepository;
import org.courierframework.core.search.Searchable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

/**
 * @author long.shiyu
 * @since 1.7
 */
@SuppressWarnings("unchecked")
public abstract class JpaRepository<T, ID extends Serializable> implements PaginationRepository<T, ID> {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    protected EntityManager em;
    private Class<T> entityClz;

    @PersistenceContext
    void setPersistenceContext(EntityManager em) {
        this.em = em;
        ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
        Type[] typeArguments = parameterizedType.getActualTypeArguments();
        Type type = typeArguments[0];
        entityClz = (Class<T>) type;
    }

    @Override
    public void delete(ID id) {
        Assert.notNull(id, "The given id must not be null!");
        em.remove(em.getReference(entityClz, id));
    }

    @Override
    public void delete(ID[] ids) {
        Assert.notNull(ids, "The given ids must not be null!");
        for (ID id :
                ids) {
            em.remove(em.getReference(entityClz, id));
        }
    }

    @Override
    public void persist(T entity) {
        Assert.notNull(entity, "The entity must not be null!");
        em.persist(entity);
    }

    @Override
    public void merge(T entity) {
        Assert.notNull(entity, "The entity must not be null!");
        em.merge(entity);
    }

    @Override
    public T findOne(ID id) {
        Assert.notNull(id, "The given id must not be null!");
        return em.find(entityClz, id);
    }

    @Override
    public List<T> findByJPQL(String jpql, Object... prams) {
        Assert.notNull(jpql);
        Query query = em.createQuery(jpql);
        int index = 0;
        for (Object parameter : prams) {
            query.setParameter(index++, parameter);
        }
        return query.getResultList();
    }

    /* page begin*/
    @Override
    public Long findTotalRecords(Searchable<T> searchable) {
        Assert.notNull(searchable);
        logger.info("[ count jpql ] - [ " + searchable.generateCountJpql(entityClz) + " ]");
        return (Long) em.createQuery(searchable.generateCountJpql(entityClz)).getSingleResult();
    }

    @Override
    public Long findFilteredRecordsBySearch(Searchable<T> searchable) {
        Assert.notNull(searchable);
        logger.info("[ filtered count jpql ] - [ " + searchable.generateFilteredCountJpql(entityClz) + " ]");
        Query query = em.createQuery(searchable.generateFilteredCountJpql(entityClz));
        initParameters(query, searchable.getParameters());
        return (Long) query.getSingleResult();
    }

    @Override
    public List<T> findDataBySearch(Searchable<T> searchable) {
        Assert.notNull(searchable);
        logger.info("[ filtered data jpql ] - [ " + searchable.generateFilteredDataJpql(entityClz) + " ]");
        Query query = em.createQuery(searchable.generateFilteredDataJpql(entityClz));
        initParameters(query, searchable.getParameters());
        return query.setFirstResult(searchable.getStart()).setMaxResults(searchable.getLength()).getResultList();
    }

    private void initParameters(Query query, List<Object> parameters) {
        Assert.notNull(query);
        Assert.notNull(parameters);
        int index = 1;
        for (Object parameter : parameters) {
            query.setParameter(index++, parameter);
        }
    }
}
